/* Time-stamp: <2011-05-02 11:39:09 xinhaoyuan> */
/* This file is copied from JOS, so comments are ignore. This file
 * will be replaced by user terminal emulator after the system
 * initialized in future */

#include <framework/framework_i.h>

void cons_intr(int (*proc)(void));

/***** Serial I/O code *****/

#define COM1		0x3F8

#define COM_RX		0	// In:	Receive buffer (DLAB=0)
#define COM_TX		0	// Out: Transmit buffer (DLAB=0)
#define COM_DLL		0	// Out: Divisor Latch Low (DLAB=1)
#define COM_DLM		1	// Out: Divisor Latch High (DLAB=1)
#define COM_IER		1	// Out: Interrupt Enable Register
#define   COM_IER_RDI	0x01	//   Enable receiver data interrupt
#define COM_IIR		2	// In:	Interrupt ID Register
#define COM_FCR		2	// Out: FIFO Control Register
#define COM_LCR		3	// Out: Line Control Register
#define	  COM_LCR_DLAB	0x80	//   Divisor latch access bit
#define	  COM_LCR_WLEN8	0x03	//   Wordlength: 8 bits
#define COM_MCR		4	// Out: Modem Control Register
#define	  COM_MCR_RTS	0x02	// RTS complement
#define	  COM_MCR_DTR	0x01	// DTR complement
#define	  COM_MCR_OUT2	0x08	// Out2 complement
#define COM_LSR		5	// In:	Line Status Register
#define   COM_LSR_DATA	0x01	//   Data available
#define   COM_LSR_TXRDY	0x20	//   Transmit buffer avail
#define   COM_LSR_TSRE	0x40	//   Transmitter off

static bool
serial_exists;

static int
serial_proc_data(void)
{
	 if (!(inb(COM1+COM_LSR) & COM_LSR_DATA))
		  return -1;
	 return inb(COM1+COM_RX);
}

void
serial_intr(void)
{
	 if (serial_exists)
		  cons_intr(serial_proc_data);
}

static void
serial_putc(int c)
{
	 int i;
	
	 for (i = 0;
		  !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800;
		  i++)
		  delay();
	
	 outb(COM1 + COM_TX, c);
}

static void
serial_init(void)
{
	 // Turn off the FIFO
	 outb(COM1+COM_FCR, 0);
	
	 // Set speed; requires DLAB latch
	 outb(COM1+COM_LCR, COM_LCR_DLAB);
	 outb(COM1+COM_DLL, (uint8_t) (115200 / 9600));
	 outb(COM1+COM_DLM, 0);

	 // 8 data bits, 1 stop bit, parity off; turn off DLAB latch
	 outb(COM1+COM_LCR, COM_LCR_WLEN8 & ~COM_LCR_DLAB);

	 // No modem controls
	 outb(COM1+COM_MCR, 0);
	 // Enable rcv interrupts
	 outb(COM1+COM_IER, COM_IER_RDI);

	 // Clear any preexisting overrun indications and interrupts
	 // Serial port doesn't exist if COM_LSR returns 0xFF
	 serial_exists = (inb(COM1+COM_LSR) != 0xFF);
	 (void) inb(COM1+COM_IIR);
	 (void) inb(COM1+COM_RX);

}


/***** Parallel port output code *****/
// For information on PC parallel port programming, see the class References
// page.

static void
lpt_putc(int c)
{
     int i;

     for (i = 0; !(inb(0x378+1) & 0x80) && i < 12800; i++)
		  delay();
     outb(0x378+0, c);
     outb(0x378+2, 0x08|0x04|0x01);
     outb(0x378+2, 0x08);
}

/***** Text-mode CGA/VGA display output *****/

static unsigned addr_6845;
static uint16_t *crt_buf;
static uint16_t crt_pos;

void
cga_init(void)
{
     volatile uint16_t *cp;
     uint16_t was;
     unsigned pos;

     cp = (uint16_t*) (KBASE + CGA_BUF);
     was = *cp;
     *cp = (uint16_t) 0xA55A;
     if (*cp != 0xA55A) {
		  cp = (uint16_t*) (KBASE + MONO_BUF);
		  addr_6845 = MONO_BASE;
     } else {
		  *cp = was;
		  addr_6845 = CGA_BASE;
     }
	
     /* Extract cursor location */
     outb(addr_6845, 14);
     pos = inb(addr_6845 + 1) << 8;
     outb(addr_6845, 15);
     pos |= inb(addr_6845 + 1);

     crt_buf = (uint16_t*) cp;
     crt_pos = pos;
}

void
cga_putc(int c)
{
     // if no attribute given, then use black on white
     if (!(c & ~0xFF))
		  c |= 0x0700;

     switch (c & 0xff) {
     case '\b':
		  if (crt_pos > 0) {
			   crt_pos--;
			   crt_buf[crt_pos] = (c & ~0xff) | ' ';
		  }
		  break;
     case '\n':
		  crt_pos += CRT_COLS;
		  /* fallthru */
     case '\r':
		  crt_pos -= (crt_pos % CRT_COLS);
		  break;
     case '\t':
		  cons_putc(' ');
		  cons_putc(' ');
		  cons_putc(' ');
		  cons_putc(' ');
		  cons_putc(' ');
		  break;
     default:
		  crt_buf[crt_pos++] = c;		/* write the character */
		  break;
     }

     // What is the purpose of this?
     while (crt_pos >= CRT_SIZE - CRT_COLS_REV * CRT_COLS)
	 {
		  int i;

		  memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS * (CRT_COLS_REV + 1)) * sizeof(uint16_t));
		  for (i = CRT_SIZE - CRT_COLS * (CRT_COLS_REV + 1); i < CRT_SIZE - CRT_COLS * CRT_COLS_REV; i++)
			   crt_buf[i] = 0x0700 | ' ';
		  crt_pos -= CRT_COLS;
     }

     /* move that little blinky thing */
     outb(addr_6845, 14);
     outb(addr_6845 + 1, crt_pos >> 8);
     outb(addr_6845, 15);
     outb(addr_6845 + 1, crt_pos);
}

void
vga_put_sc(int index, int c)
{
     // if no attribute given, then use black on white
     if (!(c & ~0xFF))
		  c |= 0x0700;
	 crt_buf[CRT_SIZE - CRT_COLS_REV * CRT_COLS + index] = c;
}

/***** Keyboard input code *****/

#define NO		0

#define SHIFT		(1<<0)
#define CTL		(1<<1)
#define ALT		(1<<2)

#define CAPSLOCK	(1<<3)
#define NUMLOCK		(1<<4)
#define SCROLLLOCK	(1<<5)

#define E0ESC		(1<<6)

static uint8_t shiftcode[256] = 
{
     [0x1D] CTL,
     [0x2A] SHIFT,
     [0x36] SHIFT,
     [0x38] ALT,
     [0x9D] CTL,
     [0xB8] ALT
};

static uint8_t togglecode[256] = 
{
     [0x3A] CAPSLOCK,
     [0x45] NUMLOCK,
     [0x46] SCROLLLOCK
};

static uint8_t normalmap[256] =
{
     NO,   0x1B, '1',  '2',  '3',  '4',  '5',  '6',	// 0x00
     '7',  '8',  '9',  '0',  '-',  '=',  '\b', '\t',
     'q',  'w',  'e',  'r',  't',  'y',  'u',  'i',	// 0x10
     'o',  'p',  '[',  ']',  '\n', NO,   'a',  's',
     'd',  'f',  'g',  'h',  'j',  'k',  'l',  ';',	// 0x20
     '\'', '`',  NO,   '\\', 'z',  'x',  'c',  'v',
     'b',  'n',  'm',  ',',  '.',  '/',  NO,   '*',	// 0x30
     NO,   ' ',  NO,   NO,   NO,   NO,   NO,   NO,
     NO,   NO,   NO,   NO,   NO,   NO,   NO,   '7',	// 0x40
     '8',  '9',  '-',  '4',  '5',  '6',  '+',  '1',
     '2',  '3',  '0',  '.',  NO,   NO,   NO,   NO,	// 0x50
     [0xC7] KEY_HOME,	[0x9C] '\n' /*KP_Enter*/,
     [0xB5] '/' /*KP_Div*/,	[0xC8] KEY_UP,
     [0xC9] KEY_PGUP,	[0xCB] KEY_LF,
     [0xCD] KEY_RT,		[0xCF] KEY_END,
     [0xD0] KEY_DN,		[0xD1] KEY_PGDN,
     [0xD2] KEY_INS,		[0xD3] KEY_DEL
};

static uint8_t shiftmap[256] = 
{
     NO,   033,  '!',  '@',  '#',  '$',  '%',  '^',	// 0x00
     '&',  '*',  '(',  ')',  '_',  '+',  '\b', '\t',
     'Q',  'W',  'E',  'R',  'T',  'Y',  'U',  'I',	// 0x10
     'O',  'P',  '{',  '}',  '\n', NO,   'A',  'S',
     'D',  'F',  'G',  'H',  'J',  'K',  'L',  ':',	// 0x20
     '"',  '~',  NO,   '|',  'Z',  'X',  'C',  'V',
     'B',  'N',  'M',  '<',  '>',  '?',  NO,   '*',	// 0x30
     NO,   ' ',  NO,   NO,   NO,   NO,   NO,   NO,
     NO,   NO,   NO,   NO,   NO,   NO,   NO,   '7',	// 0x40
     '8',  '9',  '-',  '4',  '5',  '6',  '+',  '1',
     '2',  '3',  '0',  '.',  NO,   NO,   NO,   NO,	// 0x50
     [0xC7] KEY_HOME,	[0x9C] '\n' /*KP_Enter*/,
     [0xB5] '/' /*KP_Div*/,	[0xC8] KEY_UP,
     [0xC9] KEY_PGUP,	[0xCB] KEY_LF,
     [0xCD] KEY_RT,		[0xCF] KEY_END,
     [0xD0] KEY_DN,		[0xD1] KEY_PGDN,
     [0xD2] KEY_INS,		[0xD3] KEY_DEL
};

#define C(x) (x - '@')

static uint8_t ctlmap[256] = 
{
     NO,      NO,      NO,      NO,      NO,      NO,      NO,      NO, 
     NO,      NO,      NO,      NO,      NO,      NO,      NO,      NO, 
     C('Q'),  C('W'),  C('E'),  C('R'),  C('T'),  C('Y'),  C('U'),  C('I'),
     C('O'),  C('P'),  NO,      NO,      '\r',    NO,      C('A'),  C('S'),
     C('D'),  C('F'),  C('G'),  C('H'),  C('J'),  C('K'),  C('L'),  NO, 
     NO,      NO,      NO,      C('\\'), C('Z'),  C('X'),  C('C'),  C('V'),
     C('B'),  C('N'),  C('M'),  NO,      NO,      C('/'),  NO,      NO,
     [0x97] KEY_HOME,
     [0xB5] C('/'),		[0xC8] KEY_UP,
     [0xC9] KEY_PGUP,	[0xCB] KEY_LF,
     [0xCD] KEY_RT,		[0xCF] KEY_END,
     [0xD0] KEY_DN,		[0xD1] KEY_PGDN,
     [0xD2] KEY_INS,		[0xD3] KEY_DEL
};

static uint8_t *charcode[4] =
{
     normalmap,
     shiftmap,
     ctlmap,
     ctlmap
};

/*
 * Get data from the keyboard.  If we finish a character, return it.  Else 0.
 * Return -1 if no data.
 */
static int
kbd_proc_data(void)
{
     int c;
     uint8_t data;
     // modified -- for TCC compilation
     static uint32_t shift = 0;

     if ((inb(KBSTATP) & KBS_DIB) == 0)
		  return -1;

     data = inb(KBDATAP);

     if (data == 0xE0) {
		  // E0 escape character
		  shift |= E0ESC;
		  return 0;
     } else if (data & 0x80) {
		  // Key released
		  data = (shift & E0ESC ? data : data & 0x7F);
		  shift &= ~(shiftcode[data] | E0ESC);
		  return 0;
     } else if (shift & E0ESC) {
		  // Last character was an E0 escape; or with 0x80
		  data |= 0x80;
		  shift &= ~E0ESC;
     }

     shift |= shiftcode[data];
     shift ^= togglecode[data];

     c = charcode[shift & (CTL | SHIFT)][data];
     if (shift & CAPSLOCK) {
		  if ('a' <= c && c <= 'z')
			   c += 'A' - 'a';
		  else if ('A' <= c && c <= 'Z')
			   c += 'a' - 'A';
     }

     // Process special keys
     // Ctrl-Alt-Del: reboot
     if (!(~shift & (CTL | ALT)) && c == KEY_DEL) {
		  /* cprintf("Rebooting!\n"); */
		  outb(0x92, 0x3); // courtesy of Chris Frost
     }

     return c;
}

void
kbd_intr(void)
{
     cons_intr(kbd_proc_data);
}

void
kbd_init(void)
{
}

/***** General device-independent console code *****/
// Here we manage the console input buffer,
// where we stash characters received from the keyboard or serial port
// whenever the corresponding interrupt occurs.

#define CONSBUFSIZE 512

static struct {
     uint8_t buf[CONSBUFSIZE];
     uint32_t rpos;
     uint32_t wpos;
} cons;

// called by device interrupt routines to feed input characters
// into the circular console input buffer.
void
cons_intr(int (*proc)(void))
{
     int c;

     while ((c = (*proc)()) != -1) {
		  if (c == 0)
			   continue;
		  cons.buf[cons.wpos++] = c;
		  if (cons.wpos == CONSBUFSIZE)
			   cons.wpos = 0;
     }
}

// return the next input character from the console, or 0 if none waiting
int
cons_getc(void)
{
     int c;

     // poll for any pending input characters,
     // so that this function works even when interrupts are disabled
     // (e.g., when called from the kernel monitor).
     serial_intr();
     kbd_intr();

     // grab the next character from the input buffer.
     if (cons.rpos != cons.wpos) {
		  c = cons.buf[cons.rpos++];
		  if (cons.rpos == CONSBUFSIZE)
			   cons.rpos = 0;
		  return c;
     }
     return 0;
}

// output a character to the console
void
cons_putc(int c)
{
	 if (serial_exists)
		  serial_putc(c);
	 lpt_putc(c);
     cga_putc(c);
}

// initialize the console devices
void
cons_init(void)
{
     cga_init();
     kbd_init();
     serial_init();
	 
     if (!serial_exists) {
     	  cprintf("Serial port does not exist! [ENTER]\n");
		  readline(NULL);
	 }
}

// `High'-level console I/O.  Used by readline and cprintf.

void
cputchar(int c)
{
     cons_putc(c);
}

int
getchar(void)
{
     int c;

     while ((c = cons_getc()) == 0)
		  /* do nothing */;
     return c;
}

int
iscons(int fdnum)
{
     // used by readline
     return 1;
}
